package com.iflytek.iot.redis.sharding.core;


import com.iflytek.iot.redis.sharding.core.entity.RedisClientInfo;
import com.iflytek.iot.redis.sharding.core.entity.RedisShardingException;
import com.iflytek.iot.redis.sharding.core.entity.SlotConfig;
import com.iflytek.iot.redis.sharding.core.utils.RedisSlotUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author ZuRun
 * @date 2020/2/28 16:14:57
 */
@Slf4j
public abstract class AbstractRedisClientPool<T> {

    /**
     * 映射组名对应的 slot相关信息
     * Map<String, RedisClientInfo<T>> 中 映射slotName与redisClient关系
     */
    private final Map<String, GroupDetail<T>> GROUP_SLOT_CLIENT_MAPPER = new HashMap<>(8);

    Map<String, GroupDetail<T>> getGroupSlotClientMapper() {
        return GROUP_SLOT_CLIENT_MAPPER;
    }


    protected GroupDetail<T> getGroupDetailByGroupNameNotNull(String groupName) {
        GroupDetail<T> groupDetail = GROUP_SLOT_CLIENT_MAPPER.get(groupName);
        if (groupDetail == null) {
            log.warn("[未知的组] group:{}", groupName);
            throw new IllegalArgumentException("未知的组:" + groupName);
        }
        return groupDetail;
    }

    List<T> getResources(String groupName) {
        return getGroupDetailByGroupNameNotNull(groupName).getResources();
    }

    T getResource(String groupName, String slotName) {
        return getGroupDetailByGroupNameNotNull(groupName).getResource(slotName);
    }

    T getResourceByKey(String groupName, String key) {
        return getGroupDetailByGroupNameNotNull(groupName).getResourceByKey(key);
    }

    SlotConfig getSlotConfigByKey(String groupName, String key) {
        return getGroupDetailByGroupNameNotNull(groupName).getSlotConfigByKey(key);
    }

    SlotConfig getSlotConfigBySlot(String groupName, String slotName) {
        return getGroupDetailByGroupNameNotNull(groupName).getSlotConfigBySlot(slotName);
    }

    List<SlotConfig> getSlotsConfig(String groupName) {
        return getGroupDetailByGroupNameNotNull(groupName).getSlotsConfig();
    }

    @Slf4j
    protected static class GroupDetail<R> {
        private String groupName;
        /**
         * Map<String, RedisClientInfo<T>> 中 映射slotName与redisClient关系
         */
        private Map<String, RedisClientInfo<R>> slotMapper = new HashMap<>();
        /**
         * 当前组中包含的虚拟节点信息你
         */
        private RedisVirtualSlotsContainer virtualSlots = new RedisVirtualSlotsContainer();

        GroupDetail(String groupName, R resource, SlotConfig slotConfig) {
            this.groupName = groupName;
            add(resource, slotConfig);
        }

        void add(R resource, SlotConfig slotConfig) {
            RedisClientInfo<R> redisClientInfo = RedisClientInfo.instance(resource, slotConfig);
            final String slotName = redisClientInfo.getSlotName();
            slotMapper.put(slotName, redisClientInfo);
            virtualSlots.addSlot(slotName, redisClientInfo.getVirtualNodeNum());
        }

        /**
         * 获取所有连接资源
         *
         * @return
         */
        List<R> getResources() {
            List<R> lists = new ArrayList<>(slotMapper.size());
            slotMapper.forEach((s, rRedisClientInfo) -> {
                lists.add(rRedisClientInfo.getResource());
            });
            return lists;
        }

        R getResourceByKey(String key) {
            String slotName = virtualSlots.findSlot(key);
            return getResource(slotName);
        }

        R getResource(Short slotNo) {
            String slotName = RedisSlotUtils.createSlotName(slotNo);
            return getResource(slotName);
        }

        R getResource(String slotName) {
            if (log.isDebugEnabled()) {
                log.debug("[根据slotName获取客户端连接资源] slotName:{} groupName:{}", slotName, groupName);
            }
            RedisClientInfo<R> rRedisClientInfo = slotMapper.get(slotName);
            if (rRedisClientInfo == null) {
                log.warn("[未找到此slot节点] slot:{}", slotName);
                throw new RedisShardingException("未找到此slot节点:" + slotName);
            }
            return rRedisClientInfo.getResource();
        }

        List<SlotConfig> getSlotsConfig() {
            List<SlotConfig> list = new ArrayList<>();
            slotMapper.values().forEach(slotMapper -> {
                list.add(slotMapper.getSlotConfig());
            });
            return list;
        }

        SlotConfig getSlotConfigBySlot(String slotName) {
            RedisClientInfo<R> rRedisClientInfo = slotMapper.get(slotName);
            if (rRedisClientInfo == null) {
                log.warn("[未找到此slot节点] slot:{}", slotName);
                throw new RedisShardingException("未找到此slot节点:" + slotName);
            }
            return rRedisClientInfo.getSlotConfig();
        }

        SlotConfig getSlotConfigByKey(String key) {
            String slotName = virtualSlots.findSlot(key);
            return slotMapper.get(slotName).getSlotConfig();
        }
    }
}
